<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>Image Viewer</title>
  <!-- Tailwind CSS CDN -->
  <script src="https://cdn.tailwindcss.com"></script>
  <!-- Vue 3 CDN -->
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>
    /* 阻止默认的双指缩放行为 */
    .touch-action-none {
      touch-action: none;
    }

    body {
      margin: 0;
      overflow: hidden;
      /* 隐藏body的滚动条 */
    }
  </style>
</head>

<body>
  <div id="app" class="h-screen w-screen flex items-center justify-center">
    <image-viewer image-url="123.jpg"></image-viewer>
  </div>
</body>
<script>
  const { createApp, ref, onMounted, onUnmounted, computed } = Vue;

  const ImageViewer = {
    props: {
      imageUrl: {
        type: String,
        required: true,
      },
    },
    template: `
      <div class="relative touch-action-none" ref="container"
           @mousedown="onMouseDown"
           @touchstart="onTouchStart"
           @wheel="onWheel">
        <img
          :src="imageUrl"
          ref="image"
          :style="{ transform: transformStyle }"
          class="cursor-move max-w-full max-h-full"
          draggable="false"
          @load="onImageLoad"
          @error="onImageError"
        />
        <div v-if="loading" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-gray-700">
          Loading...
        </div>
         <div v-if="error" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-red-500">
           Error loading image.
        </div>
      </div>
    `,
    setup(props) {

      const container = ref(null);
      const image = ref(null);

      const scale = ref(1);
      const translateX = ref(0);
      const translateY = ref(0);
      const isDragging = ref(false);
      const startX = ref(0);
      const startY = ref(0);
      const startScale = ref(1);
      const startTranslateX = ref(0);
      const startTranslateY = ref(0);
      const loading = ref(true);
      const error = ref(false);
      const fixedCenterX = ref(null)
      const fixedCenterY = ref(null)
      const relativeCenterX = ref(null)
      const relativeCenterY = ref(null)
      const isScaling = ref(false)

      const transformStyle = computed(() => {
        return `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`;
      });
      const onImageLoad = () => {
        loading.value = false;
        error.value = false
      };
      const onImageError = () => {
        loading.value = false;
        error.value = true;
      }

      const handleMove = (clientX, clientY) => {
        if (!isDragging.value) return
        const dx = clientX - startX.value;
        const dy = clientY - startY.value;

        translateX.value = startTranslateX.value + dx;
        translateY.value = startTranslateY.value + dy;
      }


      const onMouseDown = (e) => {
        isDragging.value = true;
        startX.value = e.clientX;
        startY.value = e.clientY;
        startTranslateX.value = translateX.value;
        startTranslateY.value = translateY.value;

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);
      };

      const onMouseMove = (e) => {
        handleMove(e.clientX, e.clientY)
      };

      const onMouseUp = () => {
        isDragging.value = false;
        document.removeEventListener('mousemove', onMouseMove);
        document.removeEventListener('mouseup', onMouseUp);
      };

      const onTouchStart = (e) => {
        if (e.touches.length === 1) {
          isDragging.value = true;
          startX.value = e.touches[0].clientX;
          startY.value = e.touches[0].clientY;
          startTranslateX.value = translateX.value;
          startTranslateY.value = translateY.value;
        } else if (e.touches.length === 2) {
          isScaling.value = true
          isDragging.value = false;
          const touch1 = e.touches[0];
          const touch2 = e.touches[1];
          const dist = Math.sqrt(Math.pow(touch1.clientX - touch2.clientX, 2) + Math.pow(touch1.clientY - touch2.clientY, 2));
          startScale.value = scale.value
          startTranslateX.value = translateX.value
          startTranslateY.value = translateY.value
          startX.value = dist
          fixedCenterX.value = (touch1.clientX + touch2.clientX) / 2;
          fixedCenterY.value = (touch1.clientY + touch2.clientY) / 2;


          const imgRect = image.value.getBoundingClientRect();

          relativeCenterX.value = fixedCenterX.value - imgRect.left - imgRect.width / 2;
          relativeCenterY.value = fixedCenterY.value - imgRect.top - imgRect.height / 2;

        }
        document.addEventListener('touchmove', onTouchMove, { passive: false })
        document.addEventListener('touchend', onTouchEnd)
      };


      const onTouchMove = (e) => {
        e.preventDefault();
        if (e.touches.length === 1) {
          handleMove(e.touches[0].clientX, e.touches[0].clientY);
        } else if (e.touches.length === 2 && isScaling.value) {
          const touch1 = e.touches[0];
          const touch2 = e.touches[1];
          const currentDist = Math.sqrt(Math.pow(touch1.clientX - touch2.clientX, 2) + Math.pow(touch1.clientY - touch2.clientY, 2));
          const deltaScale = (currentDist - startX.value) / container.value.offsetWidth;
          const newScale = startScale.value + deltaScale;
          scale.value = newScale;


          let translateXCenter = relativeCenterX.value * (1 - newScale / startScale.value);
          let translateYCenter = relativeCenterY.value * (1 - newScale / startScale.value);

          translateX.value = startTranslateX.value + translateXCenter;
          translateY.value = startTranslateY.value + translateYCenter;
        }
      };

      const onTouchEnd = (e) => {
        isScaling.value = false
        fixedCenterX.value = null
        fixedCenterY.value = null
        relativeCenterX.value = null
        relativeCenterY.value = null
        document.removeEventListener('touchmove', onTouchMove)
        document.removeEventListener('touchend', onTouchEnd)
      }

      const onWheel = (e) => {
        if (!isScaling.value) {
          isScaling.value = true
          fixedCenterX.value = e.clientX;
          fixedCenterY.value = e.clientY;
          const imgRect = image.value.getBoundingClientRect();
          relativeCenterX.value = fixedCenterX.value - imgRect.left - imgRect.width / 2;
          relativeCenterY.value = fixedCenterY.value - imgRect.top - imgRect.height / 2;

        }

        const deltaScale = e.deltaY * -0.001
        const newScale = scale.value + deltaScale;
        scale.value = Math.max(0.1, newScale);

        let translateXCenter = relativeCenterX.value * (1 - scale.value / (scale.value - deltaScale));
        let translateYCenter = relativeCenterY.value * (1 - scale.value / (scale.value - deltaScale));

        translateX.value = translateX.value + translateXCenter
        translateY.value = translateY.value + translateYCenter
        clearTimeout(e.target.wheelTimer)
        e.target.wheelTimer = setTimeout(() => {
          isScaling.value = false
          fixedCenterX.value = null
          fixedCenterY.value = null
          relativeCenterX.value = null
          relativeCenterY.value = null
        }, 50);
      };

      return {
        container,
        image,
        scale,
        translateX,
        translateY,
        transformStyle,
        isDragging,
        startX,
        startY,
        startScale,
        startTranslateX,
        startTranslateY,
        onMouseDown,
        onMouseMove,
        onMouseUp,
        onTouchStart,
        onTouchMove,
        onTouchEnd,
        onWheel,
        loading,
        error,
        onImageLoad,
        onImageError,
      };
    },
  };

  const app = createApp({
    components: {
      ImageViewer,
    },
  });

  app.mount('#app');
</script>

</html>